"""World generation and block/item management."""

from __future__ import annotations

import math
import random
from dataclasses import dataclass
from typing import Iterable

import pygame

from config import BLOCK_COLORS, ITEM_COLORS, WORLD_CONFIG


@dataclass(slots=True)
class Block:
    x: int
    y: int
    block_type: str


@dataclass(slots=True)
class Item:
    x: int
    y: int
    item_type: str


class World:
    """初始化世界对象。
    
    Args:
        screen_width: 屏幕宽度（像素）
        screen_height: 屏幕高度（像素）
    """
    def __init__(self, screen_width: int, screen_height: int) -> None:
        self.screen_width = screen_width
        self.screen_height = screen_height
        self.block_size = WORLD_CONFIG.block_size
        self.ground_height = screen_height - WORLD_CONFIG.ground_offset
        self.blocks: list[Block] = []
        self.items: list[Item] = []
        self.resource_hints: dict[tuple[int, int], str] = {}
        self.rng = random.Random(WORLD_CONFIG.world_seed)
        self._generate_terrain()

    # --- generation -------------------------------------------------
    def _generate_terrain(self) -> None:
        self.blocks.clear()
        self.resource_hints.clear()
        columns = (self.screen_width // self.block_size) + 2
        heights: list[int] = []

        base = self.ground_height - 3 * self.block_size
        for col in range(columns):
            variation = self.rng.randint(-3, 3) * self.block_size
            heights.append(base + variation)

        # smooth terrain heights
        for idx in range(1, len(heights) - 1):
            heights[idx] = (
                heights[idx - 1] + heights[idx] + heights[idx + 1]
            ) // 3

        for col, height in enumerate(heights):
            x = col * self.block_size
            surface_y = min(height, self.ground_height)
            self.blocks.append(Block(x, surface_y, "grass"))

            y = surface_y + self.block_size
            while y < self.screen_height:
                block_type = "stone" if self.rng.random() < WORLD_CONFIG.ore_spawn_chance else "dirt"
                if block_type == "stone" and self.rng.random() < 0.1:
                    block_type = "ore"
                self.blocks.append(Block(x, y, block_type))
                y += self.block_size

        self._place_trees(heights)
        self._place_clouds()
        self._ensure_resource_blocks(heights)

    def regenerate(self) -> None:
        if WORLD_CONFIG.world_seed is None:
            self.rng.seed(random.randrange(1 << 30))
        self._generate_terrain()
        self.items.clear()

    def _place_trees(self, heights: list[int]) -> None:
        columns = len(heights)
        for _ in range(WORLD_CONFIG.tree_count):
            col = self.rng.randrange(columns)
            trunk_height = self.rng.randint(2, 3)
            x = col * self.block_size
            ground_y = heights[col]
            for level in range(1, trunk_height + 1):
                y = ground_y - level * self.block_size
                if y <= 0:
                    continue
                self._append_unique(Block(x, y, "tree"))

    def _place_clouds(self) -> None:
        cloud_layers = self.rng.randint(2, 4)
        for _ in range(cloud_layers):
            start_x = self.rng.randint(0, self.screen_width - self.block_size * 4)
            y = self.rng.randint(self.block_size * 2, self.ground_height // 2)
            length = self.rng.randint(3, 5)
            for offset in range(length):
                x = start_x + offset * self.block_size
                self._append_unique(Block(x, y, "cloud"))

    def _ensure_resource_blocks(self, heights: list[int]) -> None:
        columns = len(heights)
        if columns == 0:
            return

        def surface_y(column: int) -> int:
            return min(heights[column], self.ground_height)

        def find_open_column(preferred: int) -> int:
            preferred = max(0, min(columns - 1, preferred))
            candidates = [preferred]
            for delta in range(1, columns):
                left = preferred - delta
                right = preferred + delta
                if 0 <= right < columns:
                    candidates.append(right)
                if 0 <= left < columns:
                    candidates.append(left)
            for column in candidates:
                x = column * self.block_size
                y = surface_y(column)
                if not self.block_at(x, y - self.block_size):
                    return column
            return preferred

        def ensure_surface_resource(column: int, block_type: str) -> None:
            column = max(0, min(columns - 1, column))
            x = column * self.block_size
            y = surface_y(column)
            block = self.block_at(x, y)
            if block is None:
                block = Block(x, y, block_type)
                self.blocks.append(block)
            else:
                block.block_type = block_type
            self._mark_resource_hint(x, y, block_type)
            below_y = y + self.block_size
            if not self.block_at(x, below_y):
                self.blocks.append(Block(x, below_y, "dirt"))

        stone_column = find_open_column(min(columns - 1, max(1, columns // 8)))
        ore_column = find_open_column(min(columns - 1, stone_column + max(2, columns // 6)))
        if columns > 1 and ore_column == stone_column:
            ore_column = (ore_column + 1) % columns

        ensure_surface_resource(stone_column, "stone")
        ensure_surface_resource(ore_column, "ore")

        for column in range(columns):
            x = column * self.block_size
            y = surface_y(column)
            block = self.block_at(x, y)
            above = self.block_at(x, y - self.block_size)
            if block and block.block_type in {"stone", "ore"} and not above:
                self._mark_resource_hint(x, y, block.block_type)

    # --- block helpers ----------------------------------------------
    def _append_unique(self, block: Block) -> None:
        if not self.block_at(block.x, block.y):
            self.blocks.append(block)

    def _mark_resource_hint(self, x: int, y: int, block_type: str) -> None:
        self.resource_hints[(x, y)] = block_type

    def block_at(self, x: int, y: int) -> Block | None:
        for block in self.blocks:
            if block.x == x and block.y == y:
                return block
        return None

    def place_block(self, x: int, y: int, block_type: str) -> bool:
        if self.block_at(x, y):
            return False
        self.blocks.append(Block(x, y, block_type))
        return True

    def remove_block(self, x: int, y: int) -> str | None:
        for idx, block in enumerate(self.blocks):
            if block.x == x and block.y == y:
                self.blocks.pop(idx)
                self.resource_hints.pop((block.x, block.y), None)
                return self._drop_for_block(block.block_type)
        return None

    def _drop_for_block(self, block_type: str) -> str | None:
        mapping = {
            "grass": "wood",
            "tree": "wood",
            "stone": "stone",
            "ore": "ore_gem",
            "plank": "plank",
            "stone_brick": "stone_brick",
        }
        return mapping.get(block_type)

    # --- items ------------------------------------------------------
    def spawn_item(self, x: int, y: int, item_type: str) -> None:
        self.items.append(Item(x, y, item_type))

    def remove_item(self, item: Item) -> None:
        if item in self.items:
            self.items.remove(item)

    # --- rendering --------------------------------------------------
    def draw_blocks(self, screen: pygame.Surface) -> None:
        for block in self.blocks:
            color = BLOCK_COLORS.get(block.block_type, (255, 255, 255))
            rect = pygame.Rect(block.x, block.y, self.block_size, self.block_size)
            pygame.draw.rect(screen, color, rect)

    def draw_resource_hints(self, screen: pygame.Surface) -> None:
        if not self.resource_hints:
            return
        cycle = (pygame.time.get_ticks() // 150) % 10
        alpha = 110 + cycle * 14
        for (x, y), block_type in list(self.resource_hints.items()):
            block = self.block_at(x, y)
            if not block:
                self.resource_hints.pop((x, y), None)
                continue
            base_color = (255, 215, 0) if block_type == "ore" else (144, 238, 144)
            rect = pygame.Rect(x, y, self.block_size, self.block_size)
            overlay = pygame.Surface(rect.size, pygame.SRCALPHA)
            overlay.fill((*base_color, min(255, alpha)))
            screen.blit(overlay, rect.topleft)
            pygame.draw.rect(screen, base_color, rect, width=5)
            arrow_tip = (rect.centerx, rect.top - 12)
            arrow_left = (rect.centerx - 8, rect.top - 2)
            arrow_right = (rect.centerx + 8, rect.top - 2)
            pygame.draw.polygon(screen, base_color, [arrow_tip, arrow_left, arrow_right])

    def draw_items(self, screen: pygame.Surface) -> None:
        for item in self.items:
            color = ITEM_COLORS.get(item.item_type, (255, 255, 255))
            rect = pygame.Rect(item.x + 10, item.y + 10, 20, 20)
            pygame.draw.rect(screen, color, rect)

    # --- collision --------------------------------------------------
    def get_colliding_blocks(self, rect: pygame.Rect) -> Iterable[Block]:
        block_rect = pygame.Rect(0, 0, self.block_size, self.block_size)
        for block in self.blocks:
            block_rect.topleft = (block.x, block.y)
            if rect.colliderect(block_rect):
                yield block

    def get_block_rect(self, block: Block) -> pygame.Rect:
        return pygame.Rect(block.x, block.y, self.block_size, self.block_size)

    def world_bounds(self) -> pygame.Rect:
        return pygame.Rect(0, 0, self.screen_width, self.screen_height)
